package de.chessgame.gamelogic.piece;

import java.util.ArrayList;
import java.util.Iterator;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import de.chessgame.gamelogic.Board;
import de.chessgame.gamelogic.Coordinate;

public abstract class Piece {
	protected Type type;
	protected Fraction fraction;
	protected int moveCount;
	protected ArrayList<Coordinate> possibleMoves;

	public Type getType() {
		return type;
	}

	public Fraction getFraction() {
		return fraction;
	}

	public ArrayList<Coordinate> getPossibleMoves() {
		return possibleMoves;
	}

	public void setMoveCount(int moveCount) {
		this.moveCount = moveCount;
	}

	public int getMoveCount() {
		return moveCount;
	}

	/**
	 * Constructor
	 * 
	 * @param type
	 *            What <code>Type</code> has the piece. <code>KING</code>,
	 *            <code>QUEEN</code>, ...
	 * @param fraction
	 *            Is the piece <code>WHITE</code> or <code>BLACK</code>?
	 */
	public Piece(Type type, Fraction fraction) {
		this.type = type;
		this.fraction = fraction;
		this.moveCount = 0;
		this.possibleMoves = new ArrayList<Coordinate>();
	}

	/**
	 * Checks for all directions, the Piece theoretically can move to, whether
	 * it is possible with the current board position. All x, y pairs which the
	 * Piece <code>this</code> on position xOld, yOld can reach are saved in
	 * <code>possibleMoves</code>.
	 * 
	 * @param content
	 *            The board
	 * @param xOld
	 *            Old x-Position of Piece
	 * @param yOld
	 *            Old y-Position of Piece
	 */
	public void calculatePossibleMoves(Piece[][] content, int xOld, int yOld,
			boolean inCheck) {
		possibleMoves.add(new Coordinate(xOld, yOld));
	}

	/**
	 * Returns whether or not the piece can be moved
	 * 
	 * @return
	 */
	public boolean canMove(Piece[][] content, int xOld, int yOld,
			boolean inCheck) {
		return false;
	}

	/**
	 * Gets called, every time when the piece is moved. If something is changed
	 * in content, method returns <b>true</b>, otherwise <b>false</b>
	 * 
	 * @param content
	 *            The board
	 * @param xOld
	 *            Old x-Position of Piece
	 * @param yOld
	 *            Old y-Position of Piece
	 * @param x
	 *            New x-Position of Piece
	 * @param y
	 *            New y-Position of Piece
	 * @return Is something changed in <code>Piece[][] content</code>?
	 */
	public boolean onMove(Piece[][] content, int xOld, int yOld, int x, int y) {
		moveCount++;
		return false;
	}

	/**
	 * Same as onMove, but doesn't change the moveCount or other fields of the
	 * piece. Used to simulate a possible move.
	 * 
	 * @param content The board
	 * @param xOld Old x-Position of Piece
	 * @param yOld Old y-Position of Piece
	 * @param x New x-Position of Piece
	 * @param y New y-Position of Piece
	 * @return Is something changed in <code>Piece[][] content</code>?
	 */
	public boolean onMoveDontChangePiece(Piece[][] content, int xOld, int yOld,
			int x, int y) {
		return false;
	}

	/**
	 * Checks for each move that a piece theoretically can make, if the own king
	 * stands in check after you would do the move.
	 * 
	 * @param content
	 *            Board-content
	 * @param list
	 *            List of all possible moves, which are not the move, where the
	 *            piece actually doesnt move.
	 * @param xOld
	 *            x-Position of the Piece
	 * @param yOld
	 *            y-Position of the Piece
	 * @return The filtered list of moves, someone can make with the piece on
	 *         xOld yOld
	 */
	protected ArrayList<Coordinate> removeIrregularyMoves(Piece[][] content,
			ArrayList<Coordinate> list, int xOld, int yOld) {
		boolean white = fraction == Fraction.WHITE;
		if (this instanceof King) {
			for (Iterator iterator = list.iterator(); iterator.hasNext();) {
				Coordinate coord = (Coordinate) iterator.next();
				Piece[][] contentCopy = movePieceInCopy(content, xOld, yOld,
						coord.x, coord.y);
				if (Board.isKingInCheck(contentCopy, coord.x, coord.y)) {
					iterator.remove();
				}
			}
		} else {
			Coordinate kingCoord = Board.searchForKing(content, white);
			for (Iterator iterator = list.iterator(); iterator.hasNext();) {
				Coordinate coord = (Coordinate) iterator.next();
				Piece[][] contentCopy = movePieceInCopy(content, xOld, yOld,
						coord.x, coord.y);
				if (Board.isKingInCheck(contentCopy, kingCoord.x, kingCoord.y)) {
					iterator.remove();
				}
			}
		}
		return list;
	}

	/**
	 * Helpermethod, which creates a copy of the boardcontent and moves the
	 * piece on position xOld yOld to position x y
	 * 
	 * @param content
	 * @param xOld
	 * @param yOld
	 * @param x
	 * @param y
	 * @return The contentCopy, with the moved piece.
	 */
	private Piece[][] movePieceInCopy(Piece[][] content, int xOld, int yOld,
			int x, int y) {
		Piece[][] contentCopy = makeCopyOfContent(content);

		contentCopy[y][x] = this;
		contentCopy[y][x].onMoveDontChangePiece(contentCopy, xOld, yOld, x, y);

		return contentCopy;
	}

	/**
	 * Creates a copy of the content, but with the <b>same</b> references on the
	 * Pieces.
	 * 
	 * @param content
	 * @return Copy
	 */
	private Piece[][] makeCopyOfContent(Piece[][] content) {
		Piece[][] contentCopy = new Piece[content.length][content[0].length];
		for (int yTry = 0; yTry < content.length; yTry++) {
			for (int xTry = 0; xTry < content[yTry].length; xTry++) {
				contentCopy[yTry][xTry] = (Piece) content[yTry][xTry];
			}
		}
		return contentCopy;
	}

	/**
	 * Looks in horizontal and in vertical direction, beginning with the
	 * neighbor square of <code> content[y][x] </code>, whether the square is
	 * empty, there is a foe on it or it is out of border.
	 * 
	 * @param content
	 *            The board
	 * @param xOld
	 *            Old x-Position of Piece
	 * @param yOld
	 *            Old y-Position of Piece
	 * @return All x, y pairs which a <b>rook</b> on position xOld, yOld can
	 *         reach.
	 */
	protected ArrayList<Coordinate> givePossibleMovesAlongRankAndFile(
			Piece[][] content, int xOld, int yOld) {
		ArrayList<Coordinate> list = new ArrayList<Coordinate>();

		for (int moveX = 0, moveY = 1, i = 0; i < 2; moveX = 1, moveY = 0, i++) {
			for (int xDirection = -moveX, yDirection = -moveY, j = 0; j < 2; xDirection = moveX, yDirection = moveY, j++) {
				for (int xTry = xOld + xDirection, yTry = yOld + yDirection; isSquareOnBoard(
						content, xTry, yTry); xTry += xDirection, yTry += yDirection) {
					if (xTry != xOld || yTry != yOld) {
						if (content[yTry][xTry] != null) {
							if (content[yTry][xTry].getFraction() != fraction) {
								list.add(new Coordinate(xTry, yTry));
							}
							break;
						}
						list.add(new Coordinate(xTry, yTry));
					}

				}
			}
		}

		return list;
	}

	/**
	 * Looks in diagonal direction, beginning with the neighbor square of
	 * <code> content[y][x] </code>, whether the square is empty, there is a foe
	 * on it or it is out of border.
	 * 
	 * @param content
	 *            The board
	 * @param xOld
	 *            Old x-Position of Piece
	 * @param yOld
	 *            Old y-Position of Piece
	 * @return All x, y pairs which a <b>bishop</b> on position xOld, yOld can
	 *         reach.
	 */
	protected ArrayList<Coordinate> givePossibleMovesDiagonally(
			Piece[][] content, int xOld, int yOld) {
		ArrayList<Coordinate> list = new ArrayList<Coordinate>();

		for (int xDirection = -1, i = 0; i < 2; xDirection = 1, i++) {
			for (int yDirection = -1, j = 0; j < 2; yDirection = 1, j++) {
				for (int xTry = xOld + xDirection, yTry = yOld + yDirection; isSquareOnBoard(
						content, xTry, yTry); xTry += xDirection, yTry += yDirection) {
					if (xTry != xOld || yTry != yOld) {
						if (content[yTry][xTry] != null) {
							if (content[yTry][xTry].getFraction() != fraction) {
								list.add(new Coordinate(xTry, yTry));
							}
							break;
						}
						list.add(new Coordinate(xTry, yTry));
					}

				}
			}
		}

		return list;
	}

	/**
	 * Checks:
	 * <code>y >= 0 && y < content.length && x >= 0 && x < content[0].length</code>
	 * 
	 * @param content
	 *            The board
	 * @param x
	 *            x-Position
	 * @param y
	 *            yPosition
	 * @return <b>true</b> if it isn't out of border, otherwise <b>false</b>.
	 */
	protected static boolean isSquareOnBoard(Piece[][] content, int x, int y) {
		return y >= 0 && y < content.length && x >= 0 && x < content[0].length;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}

		Piece piece2 = (Piece) obj;
		return this.fraction == piece2.getFraction()
				&& this.type == piece2.getType();
	}

	/**
	 * Creates an object for the type of piece.
	 * 
	 * @param type
	 *            What <code>Type</code> has the piece. <code>KING</code>,
	 *            <code>QUEEN</code>, ...
	 * @param fraction
	 *            Is the piece <code>WHITE</code> or <code>BLACK</code>?
	 * @return <code>King</code>, <code>Queen</code>, <code>Rook</code>,
	 *         <code>Knight</code>, <code>Bishop</code> or <code>Pawn</code>.
	 */
	public static Piece createPiece(Type type, Fraction fraction) {
		if (type == null) {
			return new UndefinedPiece(fraction);
		}
		switch (type) {
		case KING:
			return new King(fraction);

		case QUEEN:
			return new Queen(fraction);

		case ROOK:
			return new Rook(fraction);

		case KNIGHT:
			return new Knight(fraction);

		case BISHOP:
			return new Bishop(fraction);

		case PAWN:
			return new Pawn(fraction);

		default:
			return null;
		}
	}

	public static Piece transformPiece(Piece piece, Type type) {
		Piece pieceNew = createPiece(type, piece.getFraction());
		pieceNew.moveCount = piece.moveCount;
		pieceNew.possibleMoves = new ArrayList<Coordinate>();
		return pieceNew;
	}

	public void writeToNBT(NBTTagCompound tagCompound) {
		byte t = (byte) Type.typeToId(type);
		tagCompound.setByte("Type", t);

		byte f = (byte) Fraction.fractionToId(fraction);
		tagCompound.setByte("Fraction", f);

		tagCompound.setInteger("MoveCount", moveCount);
	}

	public void readFromNBT(NBTTagCompound tagCompound) {
		moveCount = tagCompound.getByte("MoveCount");
	}
}
